package cn.agile4j.rpc;

import cn.agile4j.rpc.annotation.RpcService;
import cn.agile4j.rpc.pojo.RpcRequest;
import cn.agile4j.rpc.pojo.RpcResponse;
import cn.agile4j.rpc.registry.ServiceRegistry;
import cn.agile4j.rpc.utils.RpcDecoder;
import cn.agile4j.rpc.utils.RpcEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.HashMap;
import java.util.Map;

/**
 * 实现 RPC 服务器
 * 使用 Netty 可实现一个支持 NIO 的 RPC 服务器，
 * 需要使用ServiceRegistry注册服务地址
 * Created by Administrator on 2016/8/12.
 */
public class RpcServer implements ApplicationContextAware,InitializingBean{
    private static final Logger LOGGER = LoggerFactory.getLogger(RpcServer.class);

    private String serverAddress ;
    private ServiceRegistry serviceRegistry ;

    private Map<String, Object> handlerMap = new HashMap<>() ; // 存放接口名与服务对象之间的映射关系

    public RpcServer(String serverAddress) {
        this.serverAddress = serverAddress;
    }

    public RpcServer(String serverAddress, ServiceRegistry serviceRegistry) {
        this.serverAddress = serverAddress;
        this.serviceRegistry = serviceRegistry;
    }

    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        Map<String, Object> serviceBeanMap = ctx.getBeansWithAnnotation(RpcService.class) ;
        if (MapUtils.isNotEmpty(serviceBeanMap)) {
            for (Object serviceBean : serviceBeanMap.values()) {
                String interfaceName = serviceBean.getClass().getAnnotation(RpcService.class).value().getName() ;
                handlerMap.put(interfaceName, serviceBean) ;
            }
        }
    }

    public void afterPropertiesSet() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup() ;
        EventLoopGroup workerGroup = new NioEventLoopGroup() ;
        try {
            ServerBootstrap bootstrap = new ServerBootstrap() ;
            bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    .addLast(new RpcDecoder(RpcRequest.class))// 将 RPC 请求进行解码（为了处理请求）
                                    .addLast(new RpcEncoder(RpcResponse.class))// 将 RPC 响应进行编码（为了返回响应）
                                    .addLast(new RpcHandler(handlerMap));// 处理 RPC 请求
                        }
                    })
                    /**
                     * BACKLOG用于构造服务端套接字ServerSocket对象，标识当服务器请求处理线程全满时，
                     * 用于临时存放已完成三次握手的请求的队列的最大长度。
                     * 如果未设置或所设置的值小于1，Java将使用默认值50。
                     */
                    .option(ChannelOption.SO_BACKLOG, 128)
                    /**
                     * 是否启用心跳保活机制。在双方TCP套接字建立连接后（即都进入ESTABLISHED状态）
                     * 并且在两个小时左右上层没有任何数据传输的情况下，这套机制才会被激活
                     */
                    .childOption(ChannelOption.SO_KEEPALIVE, true);


        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully() ;
        }
    }
}
